// DemoMain.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "DemoMain.h"
#include "EggNineScene.h"
#include "waveMesh.h"

using namespace EggNine;

// Units: Nt
#define TRANSLATE_FORCE 1000.0f
// Units: kg/sec
#define TRANSLATE_RESISTANCE -500.0f
// Units: kg
#define OBSERVER_MASS 80.0f
// Units: m/sec
#define OBSERVER_MAX_SPEED 15.f/3.6f

#define CHECK_KEY_STATE(key) (((USHORT)GetAsyncKeyState(key))>>15)

#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE   hInst;			    					// current instance
HDC         gHdc;                                   // device context
HGLRC       gHrc;                                   // rendering context
TCHAR       szTitle[MAX_LOADSTRING];                // The title bar text
TCHAR       szWindowClass[MAX_LOADSTRING];          // the main window class name
Scene*      gScene=NULL;                            // main scene
bool        gSuspended=false;                       // suspend flag
clock_t     gTime;

// Forward declarations of functions included in this code module:
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.
    MSG msg;
    HACCEL hAccelTable;

    // Initialize global strings
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadString(hInstance, IDC_DEMOMAIN, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // Perform application initialization:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DEMOMAIN));

    // Main message loop:
    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}



//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style			= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wcex.lpfnWndProc	= WndProc;
    wcex.cbClsExtra		= 0;
    wcex.cbWndExtra		= 0;
    wcex.hInstance		= hInstance;
    wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_DEMOMAIN));
    wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_DEMOMAIN);
    wcex.lpszClassName	= szWindowClass;
    wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassEx(&wcex);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;
   DWORD exStyle;
   DWORD style;
   RECT wRect;

   hInst = hInstance; // Store instance handle in our global variable

   // 1280x720 window
   wRect.left=0;
   wRect.right=1280;
   wRect.top=0;
   wRect.bottom=720;

   exStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
   style = WS_OVERLAPPEDWINDOW;

   AdjustWindowRectEx(&wRect, style, TRUE, exStyle);

   hWnd = CreateWindowEx(   exStyle,
                            szWindowClass,
                            szTitle,
                            style | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
                            0, 0,
                            wRect.right-wRect.left,
                            wRect.bottom-wRect.top,
                            NULL,
                            NULL,
                            hInstance,
                            NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

void InitGLContext(HWND hWnd)
{
    PIXELFORMATDESCRIPTOR pfd = {
        sizeof(PIXELFORMATDESCRIPTOR),
        1,
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_GENERIC_ACCELERATED | PFD_DOUBLEBUFFER,
        PFD_TYPE_RGBA,
        32,
        0, 0, 0, 0, 0, 0,
        0,
        0,
        0,
        0, 0, 0, 0,
        24,
        8,
        0,
        0,
        0,
        0, 0, 0
    };

    gHdc=GetDC(hWnd);
    int PixelFormat=ChoosePixelFormat(gHdc, &pfd);
    SetPixelFormat(gHdc, PixelFormat, &pfd);

    HGLRC dummyRC = wglCreateContext(gHdc);
    wglMakeCurrent(gHdc, dummyRC);

    glewInit();

    // now choose a pixel format
    int pfAttribs[] = {
        WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
        WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
        WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
        WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
        WGL_COLOR_BITS_ARB, 24,
        WGL_ALPHA_BITS_ARB, 8,
        WGL_DEPTH_BITS_ARB, 24,
        WGL_STENCIL_BITS_ARB, 8,
        WGL_SWAP_METHOD_ARB, WGL_SWAP_EXCHANGE_ARB,         // This is actually possible only for full-screen
        0
    };

    UINT NumFormats;
    wglChoosePixelFormatARB(gHdc, pfAttribs, NULL, 1, &PixelFormat, &NumFormats);
    DescribePixelFormat(gHdc, PixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
    SetPixelFormat(gHdc, PixelFormat, &pfd);

    // create 4.3 core profile
    int attribs[] = {
        WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
        WGL_CONTEXT_MINOR_VERSION_ARB, 3,
        WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
        WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
        0
    };

    gHrc = wglCreateContextAttribsARB(gHdc, NULL, attribs);
    wglMakeCurrent(gHdc, gHrc);
    wglDeleteContext(dummyRC);
    wglSwapIntervalEXT(0);  // Uncap 60fps, maybe -1 is better
}

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND	- process the application menu
//  WM_PAINT	- Paint the main window
//  WM_DESTROY	- post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    static int mouseX,mouseY;
    static float dx=.0f,dy=.0f,dz=.0f;
    static float vx=.0f,vy=.0f,vz=.0f;
//     float keyAction=KEYBOARD_SENSITIVITY;

    switch (message)
    {
    case WM_COMMAND:
        wmId    = LOWORD(wParam);
        wmEvent = HIWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;
    case WM_PAINT:
    {
        clock_t tStamp=clock();
        float dt=(tStamp-gTime)*(1.0f/CLOCKS_PER_SEC);
        gTime=tStamp;
        
        // m * dv/dt = -k*v + F1 - F2
        vz+=(vz*TRANSLATE_RESISTANCE+(CHECK_KEY_STATE('S')-CHECK_KEY_STATE('W'))*TRANSLATE_FORCE)*dt/OBSERVER_MASS;
        vx+=(vx*TRANSLATE_RESISTANCE+(CHECK_KEY_STATE('D')-CHECK_KEY_STATE('A'))*TRANSLATE_FORCE)*dt/OBSERVER_MASS;
        vy+=(vy*TRANSLATE_RESISTANCE+(CHECK_KEY_STATE(VK_SPACE)-CHECK_KEY_STATE(VK_CONTROL))*TRANSLATE_FORCE)*dt/OBSERVER_MASS;

        vz=min(max(vz, -OBSERVER_MAX_SPEED), OBSERVER_MAX_SPEED);
        vx=min(max(vx, -OBSERVER_MAX_SPEED), OBSERVER_MAX_SPEED);
        vy=min(max(vy, -OBSERVER_MAX_SPEED), OBSERVER_MAX_SPEED);

        dx=vx*dt;
        dy=vy*dt;
        dz=vz*dt;

        gScene->Translate(dx, dy, dz);

        // TODO: Add any drawing code here...
        if(gScene && !gSuspended)
            gScene->Render(dt);
        SwapBuffers(gHdc);
        break;
    }
    case WM_CREATE:
        InitGLContext(hWnd);
#ifdef _DEBUG
        AllocConsole();
        FILE *tmp;
        freopen_s(&tmp, "CONIN$","rb", stdin);
        freopen_s(&tmp, "CONOUT$", "wb", stdout);
        freopen_s(&tmp, "CONOUT$", "wb", stderr);
        printf("Console active\n");
#endif
        try {
            RECT rect;
            gScene = new Scene;
//             gScene->AttachMesh(new TessWaveMesh("nTex.f32", 1e-3f, 0.1f, 1.f));
//             gScene->AttachMesh(new TestMesh);
//             gScene->AttachMesh(new DQuad("assets/cameraman.f32"));
            gScene->AttachMesh(new WaveMesh(512, 1.f, 1.f, .0002f));
            GetClientRect(hWnd, &rect);
            gScene->Reshape(rect.right-rect.left, rect.bottom-rect.top);
        }
        catch(const char* err) {
            fprintf(stderr, err);
        }
        gTime=clock();
        break;
    case WM_SIZE:
        switch(wParam) {
        case SIZE_MAXIMIZED:
            // full screen code
            // currently works as restored
        case SIZE_RESTORED:
            gSuspended=false;
            gScene->Reshape(LOWORD(lParam), HIWORD(lParam));
            break;
        case SIZE_MINIMIZED:
            gSuspended=true;
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
            break;
        }
        break;
    case WM_DESTROY:
        if(gScene)
            delete gScene;
#ifdef _DEBUG
        fclose(stdout);
        fclose(stderr);
        fclose(stdin);
        FreeConsole();
#endif
        wglDeleteContext(gHrc);
        ReleaseDC(hWnd, gHdc);
        PostQuitMessage(0);
        break;

    case WM_KEYDOWN:
        switch(wParam) {
        case VK_ESCAPE:
            SendMessage(hWnd, WM_CLOSE, 0, 0);
            return 0;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }

        break;

    case WM_MOUSEMOVE:
    {
        float dx=(float)LOWORD(lParam)-mouseX;
        float dy=(float)HIWORD(lParam)-mouseY;

        float dphi=dx*(float)M_PI/500;
        float dtheta=dy*(float)M_PI/500;

        float mag=sqrt(dx*dx+dy*dy);
        mouseX=LOWORD(lParam);
        mouseY=HIWORD(lParam);
        dx/=mag; dy/=mag;
        
        switch(wParam) {
        case MK_RBUTTON:
            gScene->RotateViewBy(dphi, dtheta);
            break;
        case MK_MBUTTON:
            gScene->RotateAroundAxis(0, 0, dx>dy ? 1.f : -1.f, mag*(float)M_PI/200);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;
    }

    case WM_LBUTTONDBLCLK:
        gScene->SelectEvent(LOWORD(lParam), HIWORD(lParam));
        break;

    case WM_MOUSEWHEEL:
        gScene->Scale(GET_WHEEL_DELTA_WPARAM(wParam) < 0 ? 9.5f/9 : 9.f/9.5f);
        break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}
